import customtkinter as ctk
import threading
import time
from lora_controller import LoraController
import lora_parameters as params
import serial.tools.list_ports
import queue
import datetime

# 设置外观主题
ctk.set_appearance_mode("System")  # 系统主题
ctk.set_default_color_theme("blue")  # 蓝色主题

class ScrollableLabelFrame(ctk.CTkFrame):
    """可滚动的标签框架"""
    
    def __init__(self, master, command=None, **kwargs):
        super().__init__(master, **kwargs)
        
        # 创建滚动条
        self.scrollbar = ctk.CTkScrollbar(self)
        self.scrollbar.grid(row=0, column=1, sticky="ns")
        
        # 创建文本框
        self.textbox = ctk.CTkTextbox(self, yscrollcommand=self.scrollbar.set, wrap="word")
        self.textbox.grid(row=0, column=0, sticky="nsew")
        self.scrollbar.configure(command=self.textbox.yview)
        
        # 配置布局
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        
        # 设置最大缓冲区大小
        self.max_lines = 1000
        
        # 时间戳格式
        self.timestamp_format = "%H:%M:%S.%f"
    
    def add_message(self, message, message_type="info"):
        """添加一条消息到文本框"""
        try:
            # 获取当前时间
            now = datetime.datetime.now()
            timestamp = now.strftime(self.timestamp_format)[:-3]
            
            # 设置不同消息类型的颜色
            color_tags = {
                "info": "black",
                "success": "green",
                "error": "red",
                "warning": "orange",
                "tx": "blue",
                "rx": "purple",
                "hex": "gray"
            }
            
            color = color_tags.get(message_type, "black")
            
            # 确保文本框可编辑
            self.textbox.configure(state="normal")
            
            # 插入消息
            self.textbox.insert("end", f"[{timestamp}] ", "timestamp")
            self.textbox.insert("end", f"{message}\n", message_type)
            
            # 设置标签颜色
            self.textbox.tag_config("timestamp", foreground="gray")
            self.textbox.tag_config(message_type, foreground=color)
            
            # 限制行数
            lines = self.textbox.get("1.0", "end-1c").count('\n')
            if lines > self.max_lines:
                self.textbox.delete("1.0", f"{lines - self.max_lines + 1}.0")
            
            # 强制滚动到底部
            self.textbox.see("end")
            self.textbox.update()  # 强制更新UI
            
            # 使文本框不可编辑
            self.textbox.configure(state="disabled")
            
            # 打印到控制台作为备份
            print(f"[{timestamp}] [{message_type}] {message}")
        except Exception as e:
            print(f"添加日志消息失败: {str(e)}")  # 错误打印到控制台
    
    def clear(self):
        """清空文本框"""
        self.textbox.configure(state="normal")
        self.textbox.delete("1.0", "end")
        self.textbox.configure(state="disabled")


class ParameterSelector(ctk.CTkFrame):
    """参数选择器组件"""
    
    def __init__(self, master, label_text, options, default_index=0, **kwargs):
        super().__init__(master, **kwargs)
        
        self.options = options
        
        # 创建标签和下拉框
        self.label = ctk.CTkLabel(self, text=label_text)
        self.label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
        
        # 准备选项字符串和值的映射
        self.option_strings = [f"{opt['name']}" for opt in options]
        self.option_values = [opt['value'] for opt in options]
        
        # 创建下拉框
        self.combobox = ctk.CTkComboBox(self, values=self.option_strings)
        self.combobox.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        
        # 设置默认值
        self.combobox.set(self.option_strings[default_index])
        
        # 配置布局
        self.grid_columnconfigure(1, weight=1)
    
    def get_value(self):
        """获取选择的值"""
        selected_string = self.combobox.get()
        index = self.option_strings.index(selected_string)
        return self.option_values[index]
    
    def set_value(self, value):
        """设置选择的值"""
        if value in self.option_values:
            index = self.option_values.index(value)
            self.combobox.set(self.option_strings[index])
    
    def get_description(self):
        """获取选择的描述"""
        selected_string = self.combobox.get()
        index = self.option_strings.index(selected_string)
        return self.options[index]['description']


class NumericEntry(ctk.CTkFrame):
    """数字输入组件"""
    
    def __init__(self, master, label_text, min_value=None, max_value=None, default_value=0, **kwargs):
        super().__init__(master, **kwargs)
        
        self.min_value = min_value
        self.max_value = max_value
        
        # 创建标签和输入框
        self.label = ctk.CTkLabel(self, text=label_text)
        self.label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
        
        # 配置验证函数
        vcmd = (self.register(self._validate), '%P')
        
        # 创建输入框
        self.entry = ctk.CTkEntry(self)
        self.entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        
        # 设置默认值
        self.entry.insert(0, str(default_value))
        
        # 配置布局
        self.grid_columnconfigure(1, weight=1)
    
    def _validate(self, new_value):
        """验证输入的值是否为有效数字"""
        if new_value == "":
            return True
        
        try:
            value = int(new_value)
            if self.min_value is not None and value < self.min_value:
                return False
            if self.max_value is not None and value > self.max_value:
                return False
            return True
        except ValueError:
            try:
                value = float(new_value)
                if self.min_value is not None and value < self.min_value:
                    return False
                if self.max_value is not None and value > self.max_value:
                    return False
                return True
            except ValueError:
                return False
    
    def get_value(self):
        """获取输入的值"""
        value = self.entry.get()
        try:
            return int(value)
        except ValueError:
            try:
                return float(value)
            except ValueError:
                return 0
    
    def set_value(self, value):
        """设置输入的值"""
        self.entry.delete(0, "end")
        self.entry.insert(0, str(value))


class FrequencySelector(ctk.CTkFrame):
    """频率选择器组件"""
    
    def __init__(self, master, default_value=470500000, **kwargs):
        super().__init__(master, **kwargs)
        
        # 创建预设选择和输入框
        self.label = ctk.CTkLabel(self, text="频率 (Hz):")
        self.label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
        
        # 准备预设选项
        self.presets = params.FREQUENCY_PRESETS
        self.preset_strings = [f"{p['name']}" for p in self.presets]
        self.preset_strings.append("自定义...")
        
        # 创建预设下拉框
        self.preset_combobox = ctk.CTkComboBox(self, values=self.preset_strings, command=self._on_preset_changed)
        self.preset_combobox.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        
        # 创建频率输入框
        self.entry = ctk.CTkEntry(self)
        self.entry.grid(row=0, column=2, padx=5, pady=5, sticky="ew")
        self.entry.insert(0, str(default_value))
        
        # 寻找最接近的预设
        closest_preset = None
        min_diff = float('inf')
        for i, preset in enumerate(self.presets):
            diff = abs(preset['value'] - default_value)
            if diff < min_diff:
                min_diff = diff
                closest_preset = i
        
        # 如果差异很小，选择预设
        if min_diff < 1000:  # 1kHz内认为是同一频率
            self.preset_combobox.set(self.preset_strings[closest_preset])
        else:
            self.preset_combobox.set("自定义...")
        
        # 配置布局
        self.grid_columnconfigure(1, weight=0)
        self.grid_columnconfigure(2, weight=1)
    
    def _on_preset_changed(self, selection):
        """预设改变时的回调"""
        if selection == "自定义...":
            # 不改变输入框的值
            return
        
        # 找到对应预设的值
        for preset in self.presets:
            if f"{preset['name']}" == selection:
                self.entry.delete(0, "end")
                self.entry.insert(0, str(preset['value']))
                break
    
    def get_value(self):
        """获取频率值"""
        try:
            return int(self.entry.get())
        except ValueError:
            return 0
    
    def set_value(self, value):
        """设置频率值"""
        self.entry.delete(0, "end")
        self.entry.insert(0, str(value))
        
        # 尝试匹配预设
        for i, preset in enumerate(self.presets):
            if abs(preset['value'] - value) < 1000:  # 1kHz内认为是同一频率
                self.preset_combobox.set(self.preset_strings[i])
                return
        
        # 如果没有匹配的预设，选择"自定义..."
        self.preset_combobox.set("自定义...")


class LoraApp(ctk.CTk):
    """LoRa测试应用主窗口"""
    
    def __init__(self):
        super().__init__()
        
        # 设置窗口属性
        self.title("LoRa模块测试工具")
        self.geometry("900x800")
        self.minsize(800, 700)
        
        # 创建控制器
        self.controller = None
        self.message_queue = queue.Queue()
        
        # 初始化地址设置标志
        self._has_set_local_address = False
        self._has_set_target_address = False
        
        # 初始化浮动按钮窗口
        self.float_send_button = None
        
        # 创建UI组件
        self._create_widgets()
        
        # 启动UI更新线程
        self.running = True
        self.update_thread = threading.Thread(target=self._update_ui_loop)
        self.update_thread.daemon = True
        self.update_thread.start()
        
        # 绑定关闭事件
        self.protocol("WM_DELETE_WINDOW", self._on_closing)
    
    def _create_widgets(self):
        """创建界面组件"""
        # 创建主框架
        self.main_frame = ctk.CTkFrame(self)
        self.main_frame.pack(fill="both", expand=True, padx=10, pady=10)
        
        # 创建顶部连接框架
        self.connection_frame = ctk.CTkFrame(self.main_frame)
        self.connection_frame.pack(fill="x", padx=10, pady=5)
        
        # 创建串口选择下拉框
        self.port_label = ctk.CTkLabel(self.connection_frame, text="串口:")
        self.port_label.pack(side="left", padx=5, pady=5)
        
        self.port_combobox = ctk.CTkComboBox(self.connection_frame, values=[])
        self.port_combobox.pack(side="left", padx=5, pady=5)
        
        # 创建刷新按钮
        self.refresh_button = ctk.CTkButton(
            self.connection_frame, text="刷新", width=60,
            command=self._refresh_serial_ports
        )
        self.refresh_button.pack(side="left", padx=5, pady=5)
        
        # 创建连接按钮
        self.connect_button = ctk.CTkButton(
            self.connection_frame, text="连接",
            command=self._toggle_connection
        )
        self.connect_button.pack(side="left", padx=5, pady=5)
        
        # 创建波特率选择下拉框
        self.baudrate_label = ctk.CTkLabel(self.connection_frame, text="波特率:")
        self.baudrate_label.pack(side="left", padx=5, pady=5)
        
        self.baudrate_combobox = ctk.CTkComboBox(
            self.connection_frame,
            values=[str(baud) for baud in params.BAUDRATE_OPTIONS]
        )
        self.baudrate_combobox.pack(side="left", padx=5, pady=5)
        self.baudrate_combobox.set(str(params.DEFAULT_SERIAL_CONFIG["baudrate"]))
        
        # 创建提示标签
        self.reminder_label = ctk.CTkLabel(
            self.connection_frame,
            text="注意: 发送前需设置本地和目标地址",
            text_color="red"
        )
        self.reminder_label.pack(side="right", padx=10, pady=5)
        
        # 创建分隔窗口，分隔选项卡和日志区域
        self.paned_window = ctk.CTkFrame(self.main_frame)
        self.paned_window.pack(fill="both", expand=True, padx=10, pady=5)
        self.paned_window.grid_columnconfigure(0, weight=1)
        self.paned_window.grid_rowconfigure(0, weight=5)  # 选项卡占比为5
        self.paned_window.grid_rowconfigure(1, weight=1)  # 日志区域占比为1
        
        # 创建参数和操作的选项卡
        self.tab_view = ctk.CTkTabview(self.paned_window)
        self.tab_view.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
        
        # 创建选项卡
        self.tab_tx = self.tab_view.add("发送")
        self.tab_rx = self.tab_view.add("接收")
        self.tab_config = self.tab_view.add("配置")
        
        # 设置默认选项卡
        self.tab_view.set("发送")
        
        # 配置发送选项卡
        self._setup_tx_tab()
        
        # 配置接收选项卡
        self._setup_rx_tab()
        
        # 配置基础配置选项卡
        self._setup_config_tab()
        
        # 创建日志框架
        self.log_frame = ctk.CTkFrame(self.paned_window)
        self.log_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
        
        # 创建日志标题和清除按钮
        self.log_header = ctk.CTkFrame(self.log_frame)
        self.log_header.pack(fill="x", pady=5)
        
        self.log_label = ctk.CTkLabel(self.log_header, text="日志:")
        self.log_label.pack(side="left", padx=5)
        
        self.clear_log_button = ctk.CTkButton(
            self.log_header, text="清除", width=60,
            command=self._clear_log
        )
        self.clear_log_button.pack(side="right", padx=5)
        
        # 创建日志显示区域
        self.log_view = ScrollableLabelFrame(self.log_frame)
        self.log_view.pack(fill="both", expand=True, padx=5, pady=5)
        
        # 刷新串口列表
        self._refresh_serial_ports()
        
        # 添加基本说明日志
        self.log_view.add_message("欢迎使用LoRa模块测试工具", "info")
        self.log_view.add_message("请选择串口并连接设备", "info")
    
    def _setup_tx_tab(self):
        """配置发送选项卡"""
        # 创建参数框架
        param_frame = ctk.CTkFrame(self.tab_tx)
        param_frame.pack(fill="x", padx=10, pady=10)
        
        # 创建频率选择器
        self.tx_freq_selector = FrequencySelector(
            param_frame,
            default_value=params.DEFAULT_CONFIG["frequency"]
        )
        self.tx_freq_selector.pack(fill="x", padx=10, pady=5)
        
        # 创建参数选择器框架
        param_selectors_frame = ctk.CTkFrame(param_frame)
        param_selectors_frame.pack(fill="x", padx=10, pady=5)
        param_selectors_frame.grid_columnconfigure(0, weight=1)
        param_selectors_frame.grid_columnconfigure(1, weight=1)
        
        # 创建扩频因子选择器
        self.tx_sf_selector = ParameterSelector(
            param_selectors_frame,
            "扩频因子:",
            params.SPREADING_FACTORS,
            default_index=params.DEFAULT_CONFIG["data_rate"]
        )
        self.tx_sf_selector.grid(row=0, column=0, padx=5, pady=5, sticky="ew")
        
        # 创建带宽选择器
        self.tx_bw_selector = ParameterSelector(
            param_selectors_frame,
            "带宽:",
            params.BANDWIDTH_OPTIONS,
            default_index=params.DEFAULT_CONFIG["bandwidth"]
        )
        self.tx_bw_selector.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        
        # 创建编码率选择器
        self.tx_cr_selector = ParameterSelector(
            param_selectors_frame,
            "编码率:",
            params.CODE_RATE_OPTIONS,
            default_index=params.DEFAULT_CONFIG["code_rate"] - 1
        )
        self.tx_cr_selector.grid(row=1, column=0, padx=5, pady=5, sticky="ew")
        
        # 创建功率输入框
        self.tx_power_entry = NumericEntry(
            param_selectors_frame,
            "功率:",
            min_value=params.POWER_RANGE["min"],
            max_value=params.POWER_RANGE["max"],
            default_value=params.DEFAULT_CONFIG["power"]
        )
        self.tx_power_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
        
        # 创建IQ转换选择器
        self.tx_iq_selector = ParameterSelector(
            param_frame,
            "IQ转换:",
            params.IQ_CONVERTED_OPTIONS,
            default_index=params.DEFAULT_CONFIG["iq_converted"]
        )
        self.tx_iq_selector.pack(fill="x", padx=10, pady=5)
        
        # 创建发送按钮框架
        tx_button_frame = ctk.CTkFrame(self.tab_tx)
        tx_button_frame.pack(fill="x", padx=10, pady=10)
        
        # 创建开始发送按钮
        self.start_tx_button = ctk.CTkButton(
            tx_button_frame, text="启动发送模式",
            command=self._start_tx_mode
        )
        self.start_tx_button.pack(side="left", padx=10, pady=10, fill="x", expand=True)
        
        # 创建停止发送按钮
        self.stop_tx_button = ctk.CTkButton(
            tx_button_frame, text="停止发送",
            command=self._stop_tx_mode,
            state="disabled"
        )
        self.stop_tx_button.pack(side="left", padx=10, pady=10, fill="x", expand=True)
        
        # 创建数据输入框架
        data_frame = ctk.CTkFrame(self.tab_tx)
        data_frame.pack(fill="both", expand=True, padx=10, pady=10)
        
        # 创建数据输入区域的主框架
        input_area_frame = ctk.CTkFrame(data_frame)
        input_area_frame.pack(fill="both", expand=True, padx=5, pady=5)
        
        # 创建数据输入标签（使用更明显的框架和颜色）
        data_label_frame = ctk.CTkFrame(input_area_frame, fg_color="#E3F2FD")
        data_label_frame.pack(fill="x", padx=5, pady=5)
        
        data_label = ctk.CTkLabel(
            data_label_frame, 
            text="发送数据:",
            font=("Arial", 14, "bold")
        )
        data_label.pack(padx=10, pady=8, anchor="w")
        
        # 创建数据输入框
        self.tx_data_textbox = ctk.CTkTextbox(input_area_frame, height=120)
        self.tx_data_textbox.pack(fill="both", expand=True, padx=5, pady=5)
        
        # 添加一条分隔线
        separator = ctk.CTkFrame(input_area_frame, height=2, fg_color="gray70")
        separator.pack(fill="x", padx=5, pady=2)
        
        # 创建底部控制区域
        bottom_frame = ctk.CTkFrame(data_frame, height=70)
        bottom_frame.pack(fill="x", side="bottom", padx=5, pady=(0, 5))
        bottom_frame.pack_propagate(False)  # 防止框架被子控件压缩
        
        # 创建发送按钮
        self.send_data_button = ctk.CTkButton(
            bottom_frame,
            text="发送数据",
            command=self._send_tx_data,
            state="disabled",
            height=50,  # 增加按钮高度使其更明显
            width=200,  # 设定最小宽度
            font=("Arial", 16, "bold"),  # 更大的字体
            fg_color="#1E88E5",  # 使用明显的蓝色
            hover_color="#0D47A1",  # 悬停时的深蓝色
            corner_radius=10  # 圆角
        )
        # 使用place布局，确保按钮在底部框架中居中
        self.send_data_button.place(relx=0.5, rely=0.5, anchor="center")
    
    def _setup_rx_tab(self):
        """配置接收选项卡"""
        # 创建参数框架
        param_frame = ctk.CTkFrame(self.tab_rx)
        param_frame.pack(fill="x", padx=10, pady=10)
        
        # 创建频率选择器
        self.rx_freq_selector = FrequencySelector(
            param_frame,
            default_value=params.DEFAULT_CONFIG["frequency"]
        )
        self.rx_freq_selector.pack(fill="x", padx=10, pady=5)
        
        # 创建参数选择器框架
        param_selectors_frame = ctk.CTkFrame(param_frame)
        param_selectors_frame.pack(fill="x", padx=10, pady=5)
        param_selectors_frame.grid_columnconfigure(0, weight=1)
        param_selectors_frame.grid_columnconfigure(1, weight=1)
        
        # 创建扩频因子选择器
        self.rx_sf_selector = ParameterSelector(
            param_selectors_frame,
            "扩频因子:",
            params.SPREADING_FACTORS,
            default_index=params.DEFAULT_CONFIG["data_rate"]
        )
        self.rx_sf_selector.grid(row=0, column=0, padx=5, pady=5, sticky="ew")
        
        # 创建带宽选择器
        self.rx_bw_selector = ParameterSelector(
            param_selectors_frame,
            "带宽:",
            params.BANDWIDTH_OPTIONS,
            default_index=params.DEFAULT_CONFIG["bandwidth"]
        )
        self.rx_bw_selector.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        
        # 创建编码率选择器
        self.rx_cr_selector = ParameterSelector(
            param_selectors_frame,
            "编码率:",
            params.CODE_RATE_OPTIONS,
            default_index=params.DEFAULT_CONFIG["code_rate"] - 1
        )
        self.rx_cr_selector.grid(row=1, column=0, padx=5, pady=5, sticky="ew")
        
        # 创建IQ转换选择器
        self.rx_iq_selector = ParameterSelector(
            param_selectors_frame,
            "IQ转换:",
            params.IQ_CONVERTED_OPTIONS,
            default_index=params.DEFAULT_CONFIG["iq_converted"]
        )
        self.rx_iq_selector.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
        
        # 创建数据格式选择框架
        format_frame = ctk.CTkFrame(param_frame)
        format_frame.pack(fill="x", padx=10, pady=5)
        
        # 创建数据格式标签
        format_label = ctk.CTkLabel(format_frame, text="接收数据格式:")
        format_label.pack(side="left", padx=10, pady=5)
        
        # 创建数据格式单选按钮变量
        self.rx_format_var = ctk.StringVar(value="hex")
        
        # 创建HEX格式单选按钮
        self.rx_format_hex = ctk.CTkRadioButton(
            format_frame, text="HEX", variable=self.rx_format_var, value="hex"
        )
        self.rx_format_hex.pack(side="left", padx=10, pady=5)
        
        # 创建字符串格式单选按钮
        self.rx_format_string = ctk.CTkRadioButton(
            format_frame, text="字符串", variable=self.rx_format_var, value="string"
        )
        self.rx_format_string.pack(side="left", padx=10, pady=5)
        
        # 创建接收按钮框架
        rx_button_frame = ctk.CTkFrame(self.tab_rx)
        rx_button_frame.pack(fill="x", padx=10, pady=10)
        
        # 创建开始接收按钮
        self.start_rx_button = ctk.CTkButton(
            rx_button_frame, text="启动接收模式",
            command=self._start_rx_mode
        )
        self.start_rx_button.pack(side="left", padx=10, pady=10, fill="x", expand=True)
        
        # 创建停止接收按钮
        self.stop_rx_button = ctk.CTkButton(
            rx_button_frame, text="停止接收",
            command=self._stop_rx_mode,
            state="disabled"
        )
        self.stop_rx_button.pack(side="left", padx=10, pady=10, fill="x", expand=True)
        
        # 创建数据显示框架
        data_frame = ctk.CTkFrame(self.tab_rx)
        data_frame.pack(fill="both", expand=True, padx=10, pady=10)
        
        # 创建数据显示标签和清除按钮框架
        data_header = ctk.CTkFrame(data_frame)
        data_header.pack(fill="x", pady=5)
        
        # 创建数据显示标签
        data_label = ctk.CTkLabel(data_header, text="接收数据:")
        data_label.pack(side="left", padx=5)
        
        # 创建清除数据按钮
        self.clear_rx_data_button = ctk.CTkButton(
            data_header, text="清除", width=60,
            command=self._clear_rx_data
        )
        self.clear_rx_data_button.pack(side="right", padx=5)
        
        # 创建数据显示区域
        self.rx_data_view = ScrollableLabelFrame(data_frame)
        self.rx_data_view.pack(fill="both", expand=True, padx=5, pady=5)
        self.rx_data_view.configure(height=150)  # 设置最小高度
    
    def _setup_config_tab(self):
        """配置基础配置选项卡"""
        # 创建地址配置框架
        address_frame = ctk.CTkFrame(self.tab_config)
        address_frame.pack(fill="x", padx=10, pady=10)
        
        # 创建地址输入框架
        address_input_frame = ctk.CTkFrame(address_frame)
        address_input_frame.pack(fill="x", padx=10, pady=10)
        address_input_frame.grid_columnconfigure(0, weight=1)
        address_input_frame.grid_columnconfigure(1, weight=1)
        
        # 创建本地地址输入框
        self.local_addr_entry = NumericEntry(
            address_input_frame,
            "本地地址:",
            min_value=params.ADDRESS_RANGE["min"],
            max_value=params.ADDRESS_RANGE["max"],
            default_value=params.DEFAULT_CONFIG["local_address"]
        )
        self.local_addr_entry.grid(row=0, column=0, padx=5, pady=5, sticky="ew")
        
        # 创建目标地址输入框
        self.target_addr_entry = NumericEntry(
            address_input_frame,
            "目标地址:",
            min_value=params.ADDRESS_RANGE["min"],
            max_value=params.ADDRESS_RANGE["max"],
            default_value=params.DEFAULT_CONFIG["target_address"]
        )
        self.target_addr_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        
        # 创建地址设置按钮框架
        address_button_frame = ctk.CTkFrame(address_frame)
        address_button_frame.pack(fill="x", padx=10, pady=10)
        
        # 创建设置本地地址按钮
        self.set_local_addr_button = ctk.CTkButton(
            address_button_frame, text="设置本地地址",
            command=self._set_local_address
        )
        self.set_local_addr_button.pack(side="left", padx=10, pady=10, fill="x", expand=True)
        
        # 创建设置目标地址按钮
        self.set_target_addr_button = ctk.CTkButton(
            address_button_frame, text="设置目标地址",
            command=self._set_target_address
        )
        self.set_target_addr_button.pack(side="left", padx=10, pady=10, fill="x", expand=True)
        
        # 创建睡眠模式框架
        sleep_frame = ctk.CTkFrame(self.tab_config)
        sleep_frame.pack(fill="x", padx=10, pady=10)
        
        # 创建睡眠模式选择器
        self.sleep_mode_selector = ParameterSelector(
            sleep_frame,
            "睡眠模式:",
            params.SLEEP_MODE_OPTIONS,
            default_index=0
        )
        self.sleep_mode_selector.pack(fill="x", padx=10, pady=10)
        
        # 创建进入睡眠按钮
        self.enter_sleep_button = ctk.CTkButton(
            sleep_frame, text="进入睡眠模式",
            command=self._enter_sleep_mode
        )
        self.enter_sleep_button.pack(padx=10, pady=10, fill="x")
    
    def _refresh_serial_ports(self):
        """刷新串口列表"""
        # 获取可用端口
        if self.controller:
            ports = self.controller.get_available_serial_ports()
        else:
            try:
                import serial.tools.list_ports
                ports = [port.device for port in serial.tools.list_ports.comports()]
            except Exception as e:
                self.log_view.add_message(f"获取串口列表失败: {str(e)}", "error")
                ports = []
        
        # 清空并重新添加端口
        self.port_combobox.configure(values=ports)
        
        # 如果有端口则选择第一个
        if ports:
            self.port_combobox.set(ports[0])
            self.log_view.add_message(f"发现 {len(ports)} 个串口设备", "info")
        else:
            self.log_view.add_message("未发现串口设备", "warning")
    
    def _toggle_connection(self):
        """切换连接状态"""
        if self.controller and self.controller.is_connected:
            self._disconnect()
        else:
            self._connect()
    
    def _connect(self):
        """连接到设备"""
        port = self.port_combobox.get()
        if not port:
            self.log_view.add_message("请选择串口", "error")
            return
        
        try:
            baudrate = int(self.baudrate_combobox.get())
        except ValueError:
            self.log_view.add_message("无效的波特率", "error")
            return
        
        # 创建控制器
        self.controller = LoraController(port, baudrate)
        
        # 设置回调
        self.controller.set_callback(self._on_data_received)
        
        # 连接设备
        if self.controller.connect():
            self.log_view.add_message(f"已连接到 {port}，波特率 {baudrate}", "success")
            self.connect_button.configure(text="断开连接")
            
            # 启用控制按钮
            self._update_buttons_state(True)
        else:
            self.log_view.add_message(f"连接 {port} 失败", "error")
            self.controller = None
    
    def _disconnect(self):
        """断开连接"""
        if self.controller:
            self.controller.disconnect()
            self.log_view.add_message("已断开连接", "info")
            self.connect_button.configure(text="连接")
            self.controller = None
            
            # 禁用控制按钮
            self._update_buttons_state(False)
            
            # 重置按钮状态
            self.start_tx_button.configure(state="normal")
            self.stop_tx_button.configure(state="disabled")
            self.send_data_button.configure(state="disabled")
            self.start_rx_button.configure(state="normal")
            self.stop_rx_button.configure(state="disabled")
            
            # 重置地址设置标志
            self._has_set_local_address = False
            self._has_set_target_address = False
    
    def _update_buttons_state(self, connected):
        """更新按钮状态"""
        state = "normal" if connected else "disabled"
        
        # 发送相关按钮
        self.start_tx_button.configure(state=state)
        
        # 接收相关按钮
        self.start_rx_button.configure(state=state)
        
        # 配置相关按钮
        self.set_local_addr_button.configure(state=state)
        self.set_target_addr_button.configure(state=state)
        self.enter_sleep_button.configure(state=state)
    
    def _start_tx_mode(self):
        """启动发送模式"""
        if not self.controller or not self.controller.is_connected:
            self.log_view.add_message("未连接到设备", "error")
            return
        
        # 检查是否已设置本地地址和目标地址
        if not hasattr(self, '_has_set_local_address') or not self._has_set_local_address:
            self.log_view.add_message("请先在配置选项卡中设置本地地址", "warning")
            self.tab_view.set("配置")
            return
        
        if not hasattr(self, '_has_set_target_address') or not self._has_set_target_address:
            self.log_view.add_message("请先在配置选项卡中设置目标地址", "warning")
            self.tab_view.set("配置")
            return
        
        # 获取参数
        freq = self.tx_freq_selector.get_value()
        data_rate = self.tx_sf_selector.get_value()
        bandwidth = self.tx_bw_selector.get_value()
        code_rate = self.tx_cr_selector.get_value()
        power = self.tx_power_entry.get_value()
        iq_converted = self.tx_iq_selector.get_value()
        
        # 记录参数
        params_str = (
            f"频率: {freq} Hz, "
            f"扩频因子: {self.tx_sf_selector.get_description()}, "
            f"带宽: {self.tx_bw_selector.get_description()}, "
            f"编码率: {self.tx_cr_selector.get_description()}, "
            f"功率: {power}, "
            f"IQ转换: {self.tx_iq_selector.get_description()}"
        )
        self.log_view.add_message(f"开始发送模式，参数: {params_str}", "info")
        
        # 启动发送模式
        if self.controller.set_tx_config(freq, data_rate, bandwidth, code_rate, power, iq_converted):
            self.log_view.add_message("已进入透传发送模式", "success")
            
            # 更新按钮状态
            self.start_tx_button.configure(state="disabled")
            self.stop_tx_button.configure(state="normal")
            
            # 创建一个新的、独立的发送数据按钮（顶层窗口）
            if hasattr(self, 'float_send_button') and self.float_send_button is not None:
                self.float_send_button.destroy()
            
            self.float_send_button = ctk.CTkToplevel(self)
            self.float_send_button.title("发送数据")
            self.float_send_button.geometry("300x100")
            self.float_send_button.resizable(False, False)
            
            # 设置窗口在前
            self.float_send_button.attributes('-topmost', True)
            self.float_send_button.transient(self)
            
            # 创建发送按钮
            send_frame = ctk.CTkFrame(self.float_send_button)
            send_frame.pack(fill="both", expand=True, padx=10, pady=10)
            
            send_label = ctk.CTkLabel(send_frame, text="准备发送数据:")
            send_label.pack(pady=5)
            
            float_button = ctk.CTkButton(
                send_frame,
                text="发送数据 ▶",
                command=self._send_tx_data,
                height=50,
                font=("Arial", 16, "bold"),
                fg_color="#4CAF50",
                hover_color="#2E7D32"
            )
            float_button.pack(fill="x", padx=10, pady=5)
            
            # 启用原来的发送按钮（作为备份）
            if hasattr(self, 'send_data_button'):
                self.send_data_button.configure(
                    state="normal", 
                    fg_color="#4CAF50",
                    hover_color="#2E7D32",
                    text="发送数据 ▶"
                )
            
            # 在日志中提示用户
            self.log_view.add_message("------ 已进入透传模式！请在数据框中输入内容并点击【发送数据】按钮 ------", "success")
            
            # 切换到Tab前端并让文本框获得焦点
            self.tab_view.set("发送")
            self.tx_data_textbox.focus_set()
            
            # 禁用接收按钮
            self.start_rx_button.configure(state="disabled")
        else:
            self.log_view.add_message("进入发送模式失败", "error")
    
    def _stop_tx_mode(self):
        """停止发送模式"""
        if not self.controller:
            return
        
        if self.controller.exit_transparent_mode():
            self.log_view.add_message("已退出发送模式", "info")
            
            # 更新按钮状态
            self.start_tx_button.configure(state="normal")
            self.stop_tx_button.configure(state="disabled")
            
            # 关闭浮动发送数据窗口
            if hasattr(self, 'float_send_button') and self.float_send_button is not None:
                self.float_send_button.destroy()
                self.float_send_button = None
            
            # 禁用发送按钮并恢复原样式
            if hasattr(self, 'send_data_button'):
                self.send_data_button.configure(
                    state="disabled",
                    fg_color="#1E88E5",  # 恢复蓝色
                    hover_color="#0D47A1",  # 恢复深蓝色
                    text="发送数据"  # 恢复原始文本
                )
            
            # 启用接收按钮
            self.start_rx_button.configure(state="normal")
        else:
            self.log_view.add_message("退出发送模式失败", "error")
    
    def _send_tx_data(self):
        """发送数据"""
        if not self.controller or not self.controller.in_transparent_mode:
            self.log_view.add_message("未处于发送模式", "error")
            return
        
        # 获取数据
        data = self.tx_data_textbox.get("1.0", "end-1c")
        if not data:
            self.log_view.add_message("请输入要发送的数据", "warning")
            return
        
        # 发送数据
        if self.controller.send_transparent_data(data):
            self.log_view.add_message(f"已发送: {data}", "tx")
            
            # 清空输入框，方便继续输入
            self.tx_data_textbox.delete("1.0", "end")
            self.tx_data_textbox.focus_set()
        else:
            self.log_view.add_message("发送数据失败", "error")
    
    def _start_rx_mode(self):
        """启动接收模式"""
        if not self.controller or not self.controller.is_connected:
            self.log_view.add_message("未连接到设备", "error")
            return
        
        # 检查是否已设置本地地址
        if not hasattr(self, '_has_set_local_address') or not self._has_set_local_address:
            self.log_view.add_message("请先在配置选项卡中设置本地地址", "warning")
            self.tab_view.set("配置")
            return
        
        # 获取参数
        freq = self.rx_freq_selector.get_value()
        data_rate = self.rx_sf_selector.get_value()
        bandwidth = self.rx_bw_selector.get_value()
        code_rate = self.rx_cr_selector.get_value()
        iq_converted = self.rx_iq_selector.get_value()
        format_type = self.rx_format_var.get()
        
        # 记录参数
        params_str = (
            f"频率: {freq} Hz, "
            f"扩频因子: {self.rx_sf_selector.get_description()}, "
            f"带宽: {self.rx_bw_selector.get_description()}, "
            f"编码率: {self.rx_cr_selector.get_description()}, "
            f"IQ转换: {self.rx_iq_selector.get_description()}, "
            f"格式: {'HEX' if format_type == 'hex' else '字符串'}"
        )
        self.log_view.add_message(f"开始接收模式，参数: {params_str}", "info")
        
        # 启动接收模式
        success = False
        if format_type == "hex":
            success = self.controller.set_rx_config_hex(freq, data_rate, bandwidth, code_rate, iq_converted)
        else:
            success = self.controller.set_rx_config_string(freq, data_rate, bandwidth, code_rate, iq_converted)
        
        if success:
            self.log_view.add_message("已开始接收数据", "success")
            
            # 更新按钮状态
            self.start_rx_button.configure(state="disabled")
            self.stop_rx_button.configure(state="normal")
            
            # 禁用发送按钮
            self.start_tx_button.configure(state="disabled")
        else:
            self.log_view.add_message("开始接收模式失败", "error")
    
    def _stop_rx_mode(self):
        """停止接收模式"""
        if not self.controller:
            return
        
        # 停止接收
        self.controller.stop_receiving()
        self.log_view.add_message("已停止接收数据", "info")
        
        # 更新按钮状态
        self.start_rx_button.configure(state="normal")
        self.stop_rx_button.configure(state="disabled")
        
        # 启用发送按钮
        self.start_tx_button.configure(state="normal")
    
    def _clear_rx_data(self):
        """清空接收数据"""
        self.rx_data_view.clear()
    
    def _clear_log(self):
        """清空日志"""
        self.log_view.clear()
    
    def _set_local_address(self):
        """设置本地地址"""
        if not self.controller or not self.controller.is_connected:
            self.log_view.add_message("未连接到设备", "error")
            return
        
        # 获取地址
        addr = self.local_addr_entry.get_value()
        
        # 设置地址
        if self.controller.set_local_address(addr):
            self.log_view.add_message(f"本地地址已设置为 {addr}", "success")
            # 设置标志，表示已设置本地地址
            self._has_set_local_address = True
        else:
            self.log_view.add_message("设置本地地址失败", "error")
    
    def _set_target_address(self):
        """设置目标地址"""
        if not self.controller or not self.controller.is_connected:
            self.log_view.add_message("未连接到设备", "error")
            return
        
        # 获取地址
        addr = self.target_addr_entry.get_value()
        
        # 设置地址
        if self.controller.set_target_address(addr):
            self.log_view.add_message(f"目标地址已设置为 {addr}", "success")
            # 设置标志，表示已设置目标地址
            self._has_set_target_address = True
        else:
            self.log_view.add_message("设置目标地址失败", "error")
    
    def _enter_sleep_mode(self):
        """进入睡眠模式"""
        if not self.controller or not self.controller.is_connected:
            self.log_view.add_message("未连接到设备", "error")
            return
        
        # 获取睡眠模式
        mode = self.sleep_mode_selector.get_value()
        mode_desc = self.sleep_mode_selector.get_description()
        
        # 确认
        result = ctk.CTkInputDialog(
            text=f"确定要进入睡眠模式吗？\n模式: {mode_desc}\n\n输入'yes'确认:",
            title="进入睡眠模式确认"
        ).get_input()
        
        if result and result.lower() == "yes":
            # 设置睡眠模式
            if self.controller.set_sleep_mode(mode):
                self.log_view.add_message(f"已进入睡眠模式，模式: {mode_desc}", "success")
                
                # 断开连接，因为设备已经进入睡眠
                self._disconnect()
            else:
                self.log_view.add_message("进入睡眠模式失败", "error")
    
    def _on_data_received(self, data, data_type):
        """接收数据回调"""
        # 将消息添加到队列，避免线程问题
        self.message_queue.put((data, data_type))
        
        # 可以尝试强制触发UI更新
        self.after(10, self._process_messages)  # 在10ms后在主线程中处理消息
    
    def _process_messages(self):
        """处理消息队列中的一批消息"""
        try:
            # 处理少量消息，避免UI冻结
            for _ in range(10):  # 每次最多处理10条消息
                if self.message_queue.empty():
                    break
                data, data_type = self.message_queue.get_nowait()
                
                # 添加到日志
                self.log_view.add_message(f"接收: {data}", "rx")
                
                # 添加到接收数据显示
                self.rx_data_view.add_message(data, data_type)
        except Exception as e:
            print(f"处理消息错误: {str(e)}")
    
    def _update_ui_loop(self):
        """UI更新循环"""
        while self.running:
            # 处理消息队列
            try:
                while not self.message_queue.empty():
                    data, data_type = self.message_queue.get_nowait()
                    
                    # 添加到日志
                    self.log_view.add_message(f"接收: {data}", "rx")
                    
                    # 添加到接收数据显示
                    self.rx_data_view.add_message(data, data_type)
            except:
                pass
            
            # 短暂睡眠
            time.sleep(0.1)
    
    def _on_closing(self):
        """窗口关闭处理"""
        # 停止更新循环
        self.running = False
        
        # 关闭浮动窗口
        if hasattr(self, 'float_send_button') and self.float_send_button is not None:
            self.float_send_button.destroy()
        
        # 断开连接
        if self.controller and self.controller.is_connected:
            self.controller.disconnect()
        
        # 销毁窗口
        self.destroy()


if __name__ == "__main__":
    app = LoraApp()
    app.mainloop() 